import datetime
import logging
import os
import webapp2

from google.appengine.ext.webapp import template
from google.appengine.ext.db     import TransactionFailedError

from model import user



class RegisterPage(webapp2.RequestHandler):
    """
    Page with a form to register a new user.
    
    When rendernig a page, provides template dict errors with following kinds
    of errors (keys):
    - username_not_defined
    - username_not_unique
    - username_invalid_length
    - username_invalid_format
    - password_not_defined
    - password_invalid_length
    - password_invalid_format
    - email_not_defined
    - email_not_unique
    - email_invalid_length
    - email_invalid_format
    - name_not_defined
    - name_invalid_length
    - surname_not_defined
    - surname_invalid_length
    - date_of_birth_not_defined
    - date_of_birth_invalid
    
    """
    
    HEADER = "Registration"
    
    def get(self):
        """Page with an empty registration form."""
        self.render_page()

    def post(self):
        """User registration request."""
        
        def none_if_empty_str(string):
            """Returns None if given empty string. Returns that string otherwise."""
            
            if string:
                return string
            else:
                return None
        
        def error_encountered(errors):
            """Returns if any error key in the dictionary errors has value True."""
            
            for _, occurred in errors.iteritems():
                if occurred:
                    return True
            return False
        
        # We prefer None to empty string, because
        username          = none_if_empty_str(self.request.get("username"))
        password          = none_if_empty_str(self.request.get("password"))
        email             = none_if_empty_str(self.request.get("email"))
        name              = none_if_empty_str(self.request.get("name"))
        surname           = none_if_empty_str(self.request.get("surname"))
        date_of_birth_str = none_if_empty_str(self.request.get("date-of-birth"))
        
        logging.debug("Registration request on user with credentials: username %s, email %s, name %s, surname %s, date of birth %s" %
                      (username, email, name, surname, date_of_birth_str))
        
        errors = { }
        
        # Date parsing.
        try:
            dateFormat = '%Y-%m-%d'
            if date_of_birth_str:
                date_of_birth = datetime.datetime.strptime(date_of_birth_str, dateFormat).date()
            else:
                date_of_birth = None
        # Raised by datetime.datetime.strptime()
        except ValueError:
            errors["date_of_birth_invalid"] = True
        
        # Checking if required proprties are defined.
        errors["username_not_defined"]      = not username
        errors["password_not_defined"]      = not password
        errors["email_not_defined"]         = (not email) and (user.LIMITS["email_required"])
        errors["name_not_defined"]          = (not name) and (user.LIMITS["name_required"])
        errors["surname_not_defined"]       = (not surname) and (user.LIMITS["surname_required"])
        errors["date_of_birth_not_defined"] = (not date_of_birth_str) and (user.LIMITS["date_of_birth_required"])
            
        if not error_encountered(errors):
            # Validating username, email and password.
            try:
                user.User.check_username_availability(username)
                user.User.check_email_availability(email)
                user.User.validate_password(password)
            except user.UsernameNotUnique:
                errors["username_not_unique"]     = True
            except user.EmailNotUnique:
                errors["email_not_unique"]        = True
            except user.PasswordInvalidLength:
                errors["password_invalid_length"] = True
            except user.PasswordInvalidFormat:
                errors["password_invalid_format"] = True
        
        if not error_encountered(errors):
            # Creating User instance.
            try:
                user_obj = user.User(
                    username=username, password=password, email=email, name=name, surname=surname,
                    date_of_birth=date_of_birth
                )
                user_obj.put()
            except user.UsernameInvalidLength:
                errors["username_invalid_length"] = True
            except user.UsernameInvalidFormat:
                errors["username_invalid_format"] = True
            except user.EmailInvalidLength:
                errors["email_invalid_length"]    = True
            except user.EmailInvalidFormat:
                errors["email_invalid_format"]    = True
            except user.NameInvalidLength:
                errors["name_invalid_length"]     = True
            except user.SurnameInvalidLength:
                errors["surname_invalid_length"]  = True
            except TransactionFailedError:
                # User was created but transaction to database failed.
                logging.error("Database transaction fail while trying to store user %s" %
                    username)
                self.redirect(webapp2.uri_for("register_transaction_fail"))
                return
        
        if error_encountered(errors):    
            # There were some validation errors.
            logging.debug("Errors on registering user %s: %s" %
                (username, str(errors)))
            values = {
                "username"     : self.request.get("username"),
                "password"     : self.request.get("password"),
                "email"        : self.request.get("email"),
                "name"         : self.request.get("name"),
                "surname"      : self.request.get("surname"),
                "date_of_birth": self.request.get("date-of-birth")
            }
            self.render_page(values, errors)
        else:
            # User was successfully created and stored in the database.
            logging.info("User %s successfully registered" % username)
            self.redirect(webapp2.uri_for("register_success"))
    
    def render_page(self, values={}, errors={}):
        """Page with a user registration form with given values and errors shown."""
        
        template_values = {
            "values"         : values,
            "errors"         : errors,
            "limits"         : user.LIMITS,
            "error_messages" : user.ERROR_MESSAGES,
            "form_action_url": webapp2.uri_for("register"),
            "main_page_url"  : webapp2.uri_for("main")
        }
        template_path = os.path.join(os.path.dirname(__file__), '../templates/register.html')
        self.response.out.write(template.render(template_path, template_values))


class SuccessPage(webapp2.RequestHandler):
    """Page shown after a successful registration."""
    
    def get(self):
        template_values = {
            "header"         : RegisterPage.HEADER,
            "message"        : "You have successfully registered.",
            "navigate_to_url": webapp2.uri_for("main")
        }
        template_path = os.path.join(os.path.dirname(__file__), '../templates/start-screen-message.html')
        self.response.out.write(template.render(template_path, template_values))


class TransactionFailPage(webapp2.RequestHandler):
    """Page shown on a TransactionFailedError exception on registration."""
    
    def get(self):
        template_values = {
            "header"         : RegisterPage.HEADER,
            "message"        : "Unfortunately the registration could not be completed due to database problems. Try again in a few minutes.",
            "navigate_to_url": webapp2.uri_for("register"),
            "error"          : True
        }
        template_path = os.path.join(os.path.dirname(__file__), '../templates/start-screen-message.html')
        self.response.out.write(template.render(template_path, template_values))